import c4d
import string, time
import codecs, os, os.path, re, uuid, sys, shutil, base64

from c4d import gui, plugins, bitmaps

PLUGIN_ID = 1037223

NONE_ID = -1
EXECUTE_BTN_ID = 1000
LOADING_LAB_ID = 1001
SELECTION_HUB_ID = 1002

#---UnityVertexAnimationTag.pyp
UNITY_VERTEX_ANIMATION_TAG_ID = 1037231

#---UnityActiveTag.pyp
UNITY_ACTIVE_TAG_ID = 1037226
IS_ACTIVE_ID = 1001

#---UnityBaseSettingTag.pyp
UNITY_BASE_SETTING_TAG_ID = 1037228
INITIALIZATION_BTN = 1001
SAVE_PATH = 1002
AUTO_RENAME_BTN = 1003
ANIMATION_ATTACH_PATH = 1004
IS_LEGACY_ANIMATION = 1005
IS_LOOP_ANIMATION = 1006
ROOT_BONE_PATH = 1007
IS_ENABLE_ROOT_MOTION = 1008
IS_OUTPUT_MAT_AND_TEXTURE = 1009
PREFAB_ATTACH_PATH = 1010
ANIMATION_COMPRESSION_POS_ERROR = 1011
ANIMATION_COMPRESSION_SCALE_ERROR = 1012
ANIMATION_COMPRESSION_ROT_ERROR = 1013
DEFAULT_SHADER = 1014
RETENTION_TIME_LENGTH = 1015
ANIMATION_COMPRESSION_MODE = 1016
ANIMATION_COMPRESSION_MODE1 = 0
ANIMATION_COMPRESSION_MODE2 = 1
ANIMATION_COMPRESSION_MODE3 = 2
ANIMATION_COMPRESSION_MODE4 = 3
PURE_OUTPUT = 1017
ANIMATION_KEYFRAMED_ALL_OBJECTS = 1018
BUILD_AVATAR_INFO = 1019

#---UnityScriptTag.pyp
UNITY_SCRIPT_TAG_ID = 1037230
SCRIPT_NAME = 1003

#---UnityAnimationEventTag.pyp
UNITY_ANIMATION_EVENT_ID = 1037224
EVENT_TIME = 1001
EVENT_METHOD_NAME = 1002
EVENT_INT_ARG = 1003

#---UnityExportSelectionHubTag.pyp
EXPORT_SELECTION_HUB_PLUGIN_ID = 1037229
EXPORT_LIST = 1001
SELECTION_UUID = 1002

#---C2U ShaderConnecter.pyp
SHADER_CONNECTER_PLUGIN_ID = 1037225
SHADER_CONNECTER_C4D_DISPLAY_TEXTURE = 1000
SHADER_CONNECTER_UNITY_CONTENT = 1001

#---C2U UnityAnimationCurveTag.pyp
ANIMATION_CURVE_PLUGIN_ID = 1037227
ANIMATION_CURVE_CURVE_NAME = 1001
ANIMATION_CURVE_CURVE = 1002


#-----------------------------------------------------------------------------------------Tool Methods-------------

def Current_state_to_object(doc, op, container=c4d.BaseContainer()):
    
    if not isinstance(op, c4d.BaseObject):
        raise TypeError('expected c4d.BaseObject, got %s.' %
                        op.__class__.__name__)

    doc.SetSelection(op)
    
    c4d.CallCommand(12107)#copy
    c4d.CallCommand(12108)#paste
    collapseObj = doc.GetActiveObject()
    
    parent = op.GetUp()
    
    if not parent == None:
        collapseObj.InsertUnder(parent)

    doc.SetSelection(collapseObj)
    c4d.CallCommand(12233)#current state to object

    return collapseObj

def FilterNotCTrackObject(collection):
    result = list()
    
    for item in collection:
        if len(item.GetCTracks()) > 0:
            result.append(item)

    return result

def FirstOrDefault(collection, func):
    result = filter(func, collection)
    
    if len(result) == 0: return None
    
    return result[0]

def Where(collection, func):
    result = filter(func, collection)
    
    if len(result) == 0: return None
    
    return result

def Distinct(collection, isContainFunc):
    resultList = list()

    for item in collection:
        def firstOrDefaultPredicate(m):
            return isContainFunc(m,item)

        containValue = FirstOrDefault(collection, firstOrDefaultPredicate)
        
        if containValue != None:
            if not resultList.Contains(item):
                resultList.append(item)

    return resultList

def ConvertAll(collection, func):
    result = list()
    
    for item in collection:
        result.append(func(item))
    
    return result

def FrameToTime(doc, frame):
    return c4d.BaseTime(float(frame) / doc.GetFps())

def KeyframeClear(doc, obj):
    for item in obj.GetCTracks():
        curve = item.GetCurve()
        curve.FlushKeys()

def FilterBrotherObjectsInDoc(doc, node, predicate):
    result = list()
    _FilterBrotherObjectsInDoc(doc, node, predicate, result)
    
    return result

def _FilterBrotherObjectsInDoc(doc, node, predicate, resultList):
    if node == None:return resultList
    
    if predicate(node):
        resultList.append(node)
    
    _FilterBrotherObjectsInDoc(doc, node.GetNext(), predicate, resultList)
    
    return resultList

def FilterAllObjectsInDoc(doc, predicate):
    result = list()
    _FilterAllObjectsInDoc(doc.GetFirstObject(), predicate, result)
    
    return result

def _FilterAllObjectsInDoc(node, predicate, resultList):
    if node == None:return resultList
    
    if predicate(node):
        resultList.append(node)
    
    _FilterAllObjectsInDoc(node.GetDown(), predicate, resultList)
    _FilterAllObjectsInDoc(node.GetNext(), predicate, resultList)
    
    return resultList

def FilterParentObjectsInDoc(obj, predicate):
    result = list()
    
    _FilterParentObjectsInDoc(obj.GetUp(), predicate, result)
    
    return result

def _FilterParentObjectsInDoc(node, predicate, resultList):
    if node == None:return
    
    if predicate(node):
        resultList.append(node)
    
    _FilterParentObjectsInDoc(node.GetUp(), predicate, resultList)

def FilterChildObjectsInDoc(obj, predicate):
    result = list()
    
    for item in obj.GetChildren():
        _FilterChildObjectsInDoc(item, predicate, result)
    
    return result

def _FilterChildObjectsInDoc(node, predicate, resultList):
    if node == None:return
    
    if predicate(node):
        resultList.append(node)
    
    for item in node.GetChildren():
        _FilterChildObjectsInDoc(item, predicate, resultList)

def FindObjectThroughPath(doc, path):

    if path == None : return None;
    if path == "" : return None;

    nodeStrCollection = path.split("/")
    
    node = doc.GetFirstObject()
    i = 0
    for nodeStr in nodeStrCollection:
        if  i > 0: node = node.GetDown()
        def predicate(m): return m.GetName() == nodeStr
        matchCollection = FilterBrotherObjectsInDoc(doc, node, predicate)
        if len(matchCollection) > 0:
            node = matchCollection[0]
            
        i = i + 1
    
    return node

def GetCamerasInDoc(doc):
    
    def predicate(m) : return m.CheckType(c4d.Ocamera)
    filterList = FilterAllObjectsInDoc(doc, predicate)

    return filterList

def GetFirstTagInDoc(doc, targetTagID):

    resultList = GetTagsInDoc(doc, targetTagID)

    if len(resultList) > 0 :
        return resultList[0]
    else:
        return None

def GetTagsInDoc(doc, targetTagID):

    def predicate(m) : return m.GetTag(targetTagID) != None
    filterList = FilterAllObjectsInDoc(doc, predicate)

    result = list()

    for item in filterList:
        tags = item.GetTags()
        
        def tagPredicate(m) : return m.CheckType(targetTagID)
        tags = Where(tags, tagPredicate)

        result.extend(tags)

    return result

def GetXrefTopObjectsInDoc(doc):
    
    def predicate(m) : return m.GetType() == c4d.Oxref
    xrefObjects = FilterAllObjectsInDoc(doc, predicate)
    
    xrefTopObjects = list()
    
    for item in xrefObjects:
        parentfList = FilterParentObjectsInDoc(item, predicate)
        
        if len(parentfList) == 0:
            xrefTopObjects.append(item)
    
    return xrefTopObjects

def GetCharacterSystemTopObjectsInDoc(doc):
    
    def predicate(m) : return m.GetType() == c4d.Ocharacter
    characterObjects = FilterAllObjectsInDoc(doc, predicate)
    
    characterTopObjects = list()
    
    for item in characterObjects:
        parentfList = FilterParentObjectsInDoc(item, predicate)
        
        if len(parentfList) == 0:
            characterTopObjects.append(item)
    
    return characterTopObjects

def GetRenderOffTopObjectsInDoc(doc):
    
    def predicate(m) : return m.GetRenderMode() == c4d.MODE_OFF
    editorOffObjects = FilterAllObjectsInDoc(doc, predicate)
    
    editorOffTopObjects = list()
    
    for item in editorOffObjects:
        parentEditorOffList = FilterParentObjectsInDoc(item, predicate)
        
        if len(parentEditorOffList) == 0:
            editorOffTopObjects.append(item)
    
    return editorOffTopObjects

def GetChildEditorOffObjects(node, resultList):
    _GetChildEditorOffObjects(node,node,resultList)

def GetChildEditorOffObjects(root, node, resultList):
    
    if node == None:return
    
    if node.GetEditorMode() == c4d.MODE_OFF and node != root:
        resultList.append(node)
    
    for item in node.GetChildren():
        _GetChildEditorOffObjects(root, item, resultList)

def GetParentXrefObjects(node):
    def predicate(m) : return m.GetType() == c4d.Oxref
    return FilterParentObjectsInDoc(node, predicate)

def GetChildXrefObjects(node):
    def predicate(m) : return m.GetType() == c4d.Oxref
    return FilterChildObjectsInDoc(node, predicate)

def IsParentXrefObject(node):
    
    if node == None:return False
    
    if node.GetType() == c4d.Oxref :
        return True
    
    result = False
    if IsParentXrefObject(node.GetUp()):
        result = True
        
    return result

def GetObjectSecurityPath(object):

    current = object
    result = current.GetName()

    while current.GetUp() != None:
        current = current.GetUp()
        result = current.GetName()+"/"+result

    match = re.compile(r'[^0-9|a-z|/]', re.IGNORECASE)
    result = match.sub('_', result, 0)
    return result

def ConvertSecurityString(string):
    return base64.b64encode(string.encode("utf-8"))

#-----------------------------------------------------------------------------------------    

def LoadAndActiveDoc(path):
    newDoc = c4d.documents.LoadDocument(path, c4d.SCENEFILTER_OBJECTS|c4d.SCENEFILTER_MATERIALS)
    c4d.documents.InsertBaseDocument(newDoc)
    c4d.documents.SetActiveDocument(newDoc)
    return newDoc

def CloneDoc(doc):
    
    sourceFileName = "{0}/{1}".format(doc.GetDocumentPath(), doc.GetDocumentName()).replace("\\", "/")
    newFileName = "{0}/{1}_Shadow.c4d".format(doc.GetDocumentPath(), doc.GetDocumentName()).replace("\\", "/")
    
    c4d.CallCommand(12098)#Save Document
    c4d.CallCommand(12664)#Close Document
    shutil.copy(sourceFileName, newFileName)
    
    newDoc = LoadAndActiveDoc(newFileName)
    os.remove(newFileName)
    
    return (newDoc,sourceFileName)

def CopyImage(doc, path, newPath, docPath):
 
    if path == None or path == "": return
    
    fileName = ""
    
    path = path.replace("\\","/")
    
    if path.find("/") > -1:
        fileName = os.path.basename(path)
    else:
        texStr = ""
        fileName = path
        docDir = os.path.dirname(docPath)
        path = os.path.join(docDir , "tex" , fileName).replace("\\","/")
        
        if not os.path.exists(path):
            path = os.path.join(docDir, fileName).replace("\\","/")
    try:
        sourceFilePath = path
        dstFilePath = os.path.join(newPath, fileName).replace("\\","/")
        
        flag = False
        
        if os.path.exists(dstFilePath):
            if not os.path.getsize(sourceFilePath) == os.path.getsize(dstFilePath):
                flag = True
        
        if flag :
            print "file equals"
        else:
            shutil.copyfile(sourceFilePath, dstFilePath)
            
    except:
         print "Copy Texture Error!"
         print "sourceFilePath", sourceFilePath
         print "dstFilePath", dstFilePath
    
def ExportLinkedTextures(doc, newPath):

    mats = doc.GetMaterials()

    for item in mats:
        doc.SetActiveMaterial(item)

        path = ""

        colorShader = item[c4d.MATERIAL_COLOR_SHADER]

        if colorShader == None: continue

        if colorShader.CheckType(SHADER_CONNECTER_PLUGIN_ID):

            userDataContainer = colorShader.GetUserDataContainer()
            
            id, bc = userDataContainer[1]
            projPath = colorShader[id]
            
            for id, bc in userDataContainer:

                propName = bc[c4d.DESC_NAME]
                value = colorShader[id]
                dtype = id[1].dtype

                if dtype == c4d.DTYPE_TEXTURE:
                    CopyImage(doc, value, newPath, projPath)

        else:

            if colorShader.CheckType(c4d.Xbitmap) == False: continue
            
            docPath = os.path.join(doc.GetDocumentPath(), doc.GetDocumentName())
            path = colorShader[c4d.BITMAPSHADER_FILENAME]
            CopyImage(doc, path, newPath, docPath)

def BuildShaderOutputInfo(doc, shader):
    name = ""
    outputStr = ""

    textureCache = None
    vector4Cache = None
    cubemapCache = [""]*6
    
    for id, bc in shader.GetUserDataContainer():

        tmpStr = ""
        propName = bc[c4d.DESC_NAME]
        value = shader[id]
        dtype = id[1].dtype

        flag = False

        if dtype == c4d.DTYPE_STATICTEXT and "(keyword)" in propName:
            tmpStr += "Name:{0}@Type:{1}@Value:{2}".format(ConvertSecurityString(propName), "bool", ConvertSecurityString(str(value)))
            flag = True
        elif dtype == c4d.DTYPE_STATICTEXT:
            name = ConvertSecurityString(value)
        elif dtype == c4d.DTYPE_VECTOR and "(rgb)" in propName:
            vector4Cache = value
        elif dtype == c4d.DTYPE_REAL and "(a)" in propName:
            valueStr = ConvertSecurityString("{0}_{1}_{2}_{3}".format(vector4Cache.x, vector4Cache.y, vector4Cache.z, value))
            tmpStr += "Name:{0}@Type:{1}@Value:{2}".format(ConvertSecurityString(propName[:-3]), "color", valueStr)
            flag = True
        elif dtype == c4d.DTYPE_VECTOR and "(xyz)" in propName:
            vector4Cache = value
        elif dtype == c4d.DTYPE_REAL and "(w)" in propName:
            valueStr = ConvertSecurityString("{0}_{1}_{2}_{3}".format(vector4Cache.x, vector4Cache.y, vector4Cache.z, value))
            tmpStr += "Name:{0}@Type:{1}@Value:{2}".format(ConvertSecurityString(propName[:-3]), "color", valueStr)
            flag = True
            
        elif dtype == c4d.DTYPE_TEXTURE:
            textureCache = value
        elif dtype == c4d.DTYPE_REAL and "(offset.x)" in propName:
            vector4Cache.x = value
        elif dtype == c4d.DTYPE_REAL and "(offset.y)" in propName:
            vector4Cache.y = value
        elif dtype == c4d.DTYPE_REAL and "(scale.x)" in propName:
            vector4Cache.z = value
        elif dtype == c4d.DTYPE_REAL and "(scale.y)" in propName:
            propInfo = "{0}_{1}_{2}_{3}".format(vector4Cache.x, vector4Cache.y, vector4Cache.z, value)
            textureInfo = "{0}_{1}".format(ConvertSecurityString(os.path.basename(textureCache)), propInfo)
            textureInfo = ConvertSecurityString(textureInfo)
            tmpStr += "Name:{0}@Type:{1}@Value:{2}".format(ConvertSecurityString(propName[:-9]), "texture", textureInfo)
            flag = True
            
        elif dtype == c4d.DTYPE_REAL:
            tmpStr += "Name:{0}@Type:{1}@Value:{2}".format(ConvertSecurityString(propName), "float", ConvertSecurityString(str(value)))
            flag = True
        elif dtype == c4d.DTYPE_TEXTURE and "(+x)" in propName:
            cubemapCache[0] = value
        elif dtype == c4d.DTYPE_TEXTURE and "(+y)" in propName:
            cubemapCache[1] = value
        elif dtype == c4d.DTYPE_TEXTURE and "(+z)" in propName:
            cubemapCache[2] = value
        elif dtype == c4d.DTYPE_TEXTURE and "(-x)" in propName:
            cubemapCache[3] = value
        elif dtype == c4d.DTYPE_TEXTURE and "(-y)" in propName:
            cubemapCache[4] = value
        elif dtype == c4d.DTYPE_TEXTURE and "(-z)" in propName:
            cubemapCache[5] = value
            
            p_x = ConvertSecurityString(str(os.path.basename(cubemapCache[0])))
            p_y = ConvertSecurityString(str(os.path.basename(cubemapCache[1])))
            p_z = ConvertSecurityString(str(os.path.basename(cubemapCache[2])))
            n_x = ConvertSecurityString(str(os.path.basename(cubemapCache[3])))
            n_y = ConvertSecurityString(str(os.path.basename(cubemapCache[4])))
            n_z = ConvertSecurityString(str(os.path.basename(cubemapCache[5])))
            
            tmpStr += "Name:{0}@Type:{1}@Value:{2}".format(
                ConvertSecurityString(propName[:-4])
                , "cubemap"
                , ConvertSecurityString("{0}_{1}_{2}_{3}_{4}_{5}".format(p_x, p_y, p_z, n_x, n_y, n_z)))

            flag = True
        
        if flag:
            if len(outputStr) > 0:
                outputStr += "&" + tmpStr
            else:
                outputStr = tmpStr

    shader[SHADER_CONNECTER_UNITY_CONTENT] = outputStr
    
    return (name,outputStr)

def OutputMaterialInfo(doc):
    
    result = list()

    mats = doc.GetMaterials()

    for item in mats:
        linkedObjPathList = list()
        linkedShaderName = ""
        linkedShaderFieldStr = ""
        name = item.GetName()
        imageName = ""
        colorShader = item[c4d.MATERIAL_COLOR_SHADER]
        
        if colorShader != None:
            if colorShader.CheckType(SHADER_CONNECTER_PLUGIN_ID):
                shaderName, fieldStr = BuildShaderOutputInfo(doc, colorShader)
                linkedShaderName = shaderName
                linkedShaderFieldStr = fieldStr
            else:
                if colorShader[c4d.BITMAPSHADER_FILENAME] == None:
                    print "Mat Shader Link Is Null: shader: ",colorShader.GetName(), " Mat: ",item.GetName()
                    continue
                    
                imageName = os.path.basename(colorShader[c4d.BITMAPSHADER_FILENAME])

        doc.SetActiveMaterial(item)
        c4d.CallCommand(16370)#Selection linked image.
        activeObjs = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_0)
        
        if len(activeObjs) == 0:
            print "Ignore Material, Because This Material Not Used! ", name
            continue;
        
        for linkedObj in activeObjs:
            linkedObjPathList.append(GetObjectSecurityPath(linkedObj))

        result.append((name, imageName, linkedObjPathList, linkedShaderName, linkedShaderFieldStr))
    
    return result

def BuildMaterialLinkedObjString(linkedObjPathList):
    result = ""
    
    i = 0
    
    for item in linkedObjPathList:
        if i > 0:
            result = result+"@"
        
        result = result + ConvertSecurityString(item)
        
        i = i+1
        
    return result

def BuildMaterialInfoString(doc):
    result = ""
    
    matInfoList = OutputMaterialInfo(doc)
    i = 0
    
    for item in matInfoList:
        name, imageName, linkedObjPathList, linkedShaderName, linkedShaderFieldStr = item
        
        if i > 0:
            result = result+","
        
        linkedObjInfoStr = BuildMaterialLinkedObjString(linkedObjPathList)

        name = ConvertSecurityString(name)
        imageName = ConvertSecurityString(imageName)
        
        result = result + "matName-{0}#linkedImg-{1}#linkedObj-{2}#linkedShaderName-{3}#linkedShaderField-{4}".format(name, imageName, linkedObjInfoStr, linkedShaderName, linkedShaderFieldStr)

        i = i+1
    
    return result
    
def GetScriptTagArgumentInfo(tag):

    result = ""
    i = 1
    for id, bc in tag.GetUserDataContainer():
    
        name = bc[c4d.DESC_NAME]
        finalTypeStr = ""
        fieldType = type(tag[c4d.ID_USERDATA,i])

        if id[1].dtype == c4d.DTYPE_LONG:
            finalTypeStr = "int"
        elif id[1].dtype == c4d.DTYPE_REAL:
            finalTypeStr = "float"
        elif id[1].dtype == c4d.DTYPE_BOOL:
            finalTypeStr = "bool"
        elif id[1].dtype == c4d.DTYPE_STRING:
            finalTypeStr = "string"
        elif id[1].dtype == c4d.DTYPE_COLOR:
            finalTypeStr = "color"
        elif id[1].dtype == c4d.DTYPE_VECTOR:
            finalTypeStr = "vector"

        value = str(tag[id])
        
        if i > 1:
            result = result + "&"
            
        outputName = name
        outputType = finalTypeStr
        outputValue = ConvertSecurityString(value)
        result += "name:{0}@type:{1}@value:{2}".format(outputName, outputType, outputValue)
        
        i = i+1
        
    return result

def BuildStaticInfoString(doc):
    #Get current frame info,so when start must be rewind to first frame.
    result = ""

#-----------------------------------------------------------------------
    
    #---Game Object Attach---
    gameObjectInfoList = GetTagsInDoc(doc, UNITY_ACTIVE_TAG_ID)
    
    if len(gameObjectInfoList) > 0 :
        i = 0
        for item in gameObjectInfoList:
            if i > 0:
                result = result + ","
                
            path = GetObjectSecurityPath(item.GetObject())
            result = result + "path-" + path + "#componentName-UnityEngine.GameObject#arguments-name:active@type:bool@value:" + ConvertSecurityString(str(item[IS_ACTIVE_ID]))
            i = i+1

#-----------------------------------------------------------------------
            
    scriptInfoList = GetTagsInDoc(doc, UNITY_SCRIPT_TAG_ID)
    
    if len(scriptInfoList) > 0 and len(result) > 0:
        result = result + ","

    #---Custom Script Attach---
    i = 0
    for item in scriptInfoList:
        if i > 0:
            result = result + ","
            
        path = GetObjectSecurityPath(item.GetObject())
        result = result + "path-" + path + "#componentName-" + item[SCRIPT_NAME] + "#arguments-" + GetScriptTagArgumentInfo(item)
        i = i+1
    
#-----------------------------------------------------------------------
    
    cameras = GetCamerasInDoc(doc)
    if len(cameras) > 0 and len(result) > 0:
        result = result+","
    
    #---Camera Attach---
    i = 0
    for item in cameras:
    
        if i > 0:
            result = result+","
            
        path = GetObjectSecurityPath(item)
        result = result + "path-" + path + "#componentName-UnityEngine.Camera#arguments-"
        i = i+1
    
#-----------------------------------------------------------------------
    
    vertexAnimTags = GetTagsInDoc(doc, UNITY_VERTEX_ANIMATION_TAG_ID)
    if len(vertexAnimTags) > 0 and len(result) > 0:
        result = result+","
    #---Vertex Animation Attach---
    i = 0
    for item in vertexAnimTags:
    
        if i > 0:
            result = result + ","
            
        path = GetObjectSecurityPath(item.GetObject())
        result = result + "path-" + path + "#componentName-C4DVetexAnimator#arguments-"
        i = i+1

#-----------------------------------------------------------------------

    return result

def BuildBaseInfoString_GetRootBoneObject(doc, rootBonePath):
    if rootBonePath == None: return None
    
    return FindObjectThroughPath(doc, rootBonePath)
    
def BuildBaseInfoString(fileSourcePath, rootBoneObject, baseSettingTag, frameRate):
    path = os.path.abspath(baseSettingTag[SAVE_PATH])
    animationAttachPath = ""
    prefabAttachPath = ""
    
    if not baseSettingTag[ANIMATION_ATTACH_PATH] == None and not baseSettingTag[ANIMATION_ATTACH_PATH] == "":
        animationAttachPath = os.path.abspath(baseSettingTag[ANIMATION_ATTACH_PATH])
        
    if not baseSettingTag[PREFAB_ATTACH_PATH] == None and not baseSettingTag[PREFAB_ATTACH_PATH] == "":
        prefabAttachPath = os.path.abspath(baseSettingTag[PREFAB_ATTACH_PATH])

    isLegacyAnimationStr = ""
    if baseSettingTag[IS_LEGACY_ANIMATION] == 1:
        isLegacyAnimationStr = "true"
    else:
        isLegacyAnimationStr = "false"

    isLoopAnimationStr = ""
    if baseSettingTag[IS_LOOP_ANIMATION] == 1:
        isLoopAnimationStr = "true"
    else:
        isLoopAnimationStr = "false"
    
    isApplyRootMotion = "false"
    if baseSettingTag[IS_ENABLE_ROOT_MOTION] == 1:
        isApplyRootMotion = "true"

    keyframedAllObjects = "false"
    if baseSettingTag[ANIMATION_KEYFRAMED_ALL_OBJECTS] == 1:
        keyframedAllObjects = "true"
        
    buildAvatarInfo = "false"
    if baseSettingTag[BUILD_AVATAR_INFO] == 1:
        buildAvatarInfo = "true"
        
    rootBoneName = ""
    if rootBoneObject == None: rootBonePath = ""
    else: rootBoneName = rootBoneObject.GetName()

    animationCompressionMode = baseSettingTag[ANIMATION_COMPRESSION_MODE]

    positionError = baseSettingTag[ANIMATION_COMPRESSION_POS_ERROR]
    scaleError = baseSettingTag[ANIMATION_COMPRESSION_SCALE_ERROR]
    rotError = baseSettingTag[ANIMATION_COMPRESSION_ROT_ERROR]
    defaultShader = baseSettingTag[DEFAULT_SHADER]

    result = ""
    result = result + "FilePath-" + ConvertSecurityString(path)
    result = result + "#SourcePath-" + ConvertSecurityString(fileSourcePath)
    result = result + "#PrefabAttachPath-" + ConvertSecurityString(prefabAttachPath)
    result = result + "#AnimationAttachPath-" + ConvertSecurityString(animationAttachPath)
    result = result + "#IsLegacyAnimation-" + isLegacyAnimationStr
    result = result + "#IsLoopAnimation-" + isLoopAnimationStr
    result = result + "#IsApplyRootMotion-" + isApplyRootMotion
    result = result + "#RootBoneName-" + rootBoneName
    result = result + "#KeyframedAllObjects-" + str(keyframedAllObjects)
    result = result + "#AnimationCompressMode-"+str(animationCompressionMode)
    result = result + "#AnimationCompressPosError-" + str(positionError)
    result = result + "#AnimationCompressScaleError-" + str(scaleError)
    result = result + "#AnimationCompressRotError-" + str(rotError)
    result = result + "#BuildAvatarInfo-" + str(buildAvatarInfo)
    result = result + "#FrameRate-" + str(frameRate)
    result = result + "#DefaultShader-"+ ConvertSecurityString(defaultShader)
    
    return result

def Build_KeyframeInfoString(doc, track, isFloatValue, valueScale = 1):
    result = ""
    
    curve = track.GetCurve()
    keyCount = curve.GetKeyCount()

    j = 0
    for i in range(0,keyCount):
    
        if j > 0:
            result = result + "&"

        key = curve.GetKey(i)
        time  = key.GetTime().Get()
        if isFloatValue:
            value = key.GetValue() * valueScale
        else:
            value = key.GetGeData()

        result = result + "time:" + str(time) + "@value:" + ConvertSecurityString(str(value))
        j = j+1

    return result
    
#active keyframe info
def Build_Active_KeyframeInfoString(doc, tag):
    return Build_KeyframeInfoString(doc, tag.GetFirstCTrack(), False)

#active animation info
def Build_Active_AnimationInfoString(doc):
    result = ""
    
    resultList = GetTagsInDoc(doc, UNITY_ACTIVE_TAG_ID)
    resultList = FilterNotCTrackObject(resultList)
    
    if len(resultList) == 0 : return result
    
    for item in resultList:
        keyframeInfoString = Build_Active_KeyframeInfoString(doc, item)
        result = result + ","
            
        animPath = GetObjectSecurityPath(item.GetObject())
        result = result + "path-" + animPath + "#property-m_IsActive#type-UnityEngine.GameObject#keyframeInfo-" + keyframeInfoString
    
    result = result.strip(",")
    
    return result

#camera animation info
def Build_Camera_AnimationInfoString(doc):
    result = ""
    
    fovTrack = c4d.DescID(c4d.DescLevel(500, 19, 5103))
    nearClipTrack = c4d.DescID(c4d.DescLevel(1122, 19, 5103))
    farClipTrack = c4d.DescID(c4d.DescLevel(1128, 19, 5103))
    
    targetTrackIDs = [fovTrack, nearClipTrack, farClipTrack]
    
    camerasList = GetCamerasInDoc(doc)
    
    if len(camerasList) == 0 : return result
    
    for camera in camerasList:
    
        tracks = camera.GetCTracks()
        for track in tracks:
        
            descID = track.GetDescriptionID()
        
            if not descID in targetTrackIDs:
                continue
        
            keyframeInfoString = Build_KeyframeInfoString(doc, track, True)
            result = result + ","
            
            prop = ""
            
            if descID == fovTrack: prop = "field of view"
            elif descID == nearClipTrack : prop = "near clip plane"
            elif descID == farClipTrack : prop = "far clip plane"
            
            animPath = GetObjectSecurityPath(camera)
            result = result + "path-{0}#property-{1}#type-{2}#keyframeInfo-{3}".format(animPath, prop, "UnityEngine.Camera", keyframeInfoString)

    result = result.strip(",")
    
    return result

def Build_CustomScript_AnimationInfoString(doc):
    result = ""
    
    customScriptList = GetTagsInDoc(doc, UNITY_SCRIPT_TAG_ID)
    
    if len(customScriptList) == 0 : return result

    for customScript in customScriptList:
    
        scriptName = customScript[SCRIPT_NAME]
        tracks = customScript.GetCTracks()
        
        for track in tracks:
        
            descID = track.GetDescriptionID()
            propName = ""
            isFloatType = False
            flag = False
            for id, bc in customScript.GetUserDataContainer():
            
                if id[1] == descID[1]:
                    flag = True
                    propName = bc[c4d.DESC_NAME]
                    
                    if len(descID) > 2:#complex type.(vector,color)
                        if descID[1].dtype == c4d.DTYPE_COLOR:
                            if descID[2].id == 1000: propName += ".r"
                            elif descID[2].id == 1001: propName += ".g"
                            elif descID[2].id == 1002: propName += ".b"
                        else:#Beta function,not enabled in current version.
                            if descID[2].id == 1000: propName += ".x"
                            elif descID[2].id == 1001: propName += ".y"
                            elif descID[2].id == 1002: propName += ".z"
                    
                        isFloatType = True
                        
                    elif id[1].dtype == c4d.DTYPE_REAL:
                        isFloatType = True
                    
                    break

            if flag:
                keyframeInfoString = Build_KeyframeInfoString(doc, track, isFloatType)
                result = result + ","
                
                animPath = GetObjectSecurityPath(customScript.GetObject())
                result = result + "path-{0}#property-{1}#type-{2}#keyframeInfo-{3}".format(animPath, propName, scriptName, keyframeInfoString)

    result = result.strip(",")
    
    return result

def BuildAnimationInfoString(doc):
    
    result = ""
    activeAnimationInfoStr = Build_Active_AnimationInfoString(doc)
    cameraAnimationInfoStr = Build_Camera_AnimationInfoString(doc)
    customScriptAnimationInfoStr = Build_CustomScript_AnimationInfoString(doc)
    
    result = activeAnimationInfoStr + "," + cameraAnimationInfoStr
    result = result.strip(",")
    result = result + "," + customScriptAnimationInfoStr
    result = result.strip(",")
    
    return result;

def BuildAnimationEventInfoString(doc):
    result = ""
    tagsList = GetTagsInDoc(doc, UNITY_ANIMATION_EVENT_ID)
    
    if len(tagsList) == 0 : return result
    
    i = 0
    for item in tagsList:
        if i > 0:
            result = result + ","
            
        result = result + "FrameTime-" + str(item[EVENT_TIME]) + "#Method-" + item[EVENT_METHOD_NAME] + "#Arg-" + str(item[EVENT_INT_ARG])
        i = i+1
        
    return result

def BuildCurveInfoString(curve):
    result = ""
    
    i = 0
    for item in curve.GetKnots():
        if i > 0:
            result = result + "&"
    
        pos = item["vPos"]
    
        result += "time:{0}@value:{1}".format(pos.x, pos.y)
        i = i + 1
    
    return result

def BuildAnimatorCurveString(doc):
    result = ""
    tagsList = GetTagsInDoc(doc, ANIMATION_CURVE_PLUGIN_ID)
    
    if len(tagsList) == 0 : return result
    
    i = 0
    for item in tagsList:
        if i > 0:
            result = result + ","
        
        curveInfo = BuildCurveInfoString(item[ANIMATION_CURVE_CURVE])
        curveInfo = ConvertSecurityString(curveInfo)
        result = result + "CurveName-" + item[ANIMATION_CURVE_CURVE_NAME] + "#Value-" + curveInfo
        i = i + 1
        
    return result

def CreateGhostAnimationObject(doc, retentionTimeLength):
    newObj = c4d.BaseObject(c4d.Onull)
    doc.InsertObject(newObj)
    newObj.SetName("Ghost_AnimationObject")
    trRot=c4d.CTrack(newObj, c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0)))
    curve = trRot.GetCurve()

    if retentionTimeLength:

        timePoint2 = doc.GetMaxTime()
        timePoint1 = timePoint2 - c4d.BaseTime(1, doc.GetFps())
    
        key = curve.AddKey(timePoint1)
        key["key"].SetValue(curve, 0.001)
        key = curve.AddKey(timePoint2)
        key["key"].SetValue(curve, 0)
    else:

        key = curve.AddKey(FrameToTime(doc, 0))
        key["key"].SetValue(curve, 0.001)
        key = curve.AddKey(FrameToTime(doc, 1))
        key["key"].SetValue(curve, 0)

    newObj.InsertTrackSorted(trRot)
    
    return newObj

def NodeXrefReplace(doc):

    xRefObjectsList = GetXrefTopObjectsInDoc(doc)
    
    for item in xRefObjectsList:

        doc.SetSelection(item)
        c4d.CallCommand(12236)#Convert editable

def CharacterObjectReplace(doc):

    characterObjectsList = GetCharacterSystemTopObjectsInDoc(doc)
    
    for item in characterObjectsList:
    
        doc.SetSelection(item)
        c4d.CallCommand(12236)#convert editable(just character object)

def RenderOffObjectRemove(doc):

    topObjectList = GetRenderOffTopObjectsInDoc(doc)
    
    for item in topObjectList:
        item.Remove()

def ReplaceNodesName(doc):
    _ReplaceNodesName(0, doc.GetFirstObject(), doc)

def _ReplaceNodesName(index, node, doc):

    i = index
    if node == None:return i
    
    name = node.GetName()

    node.SetName(str(i) + "_C2U_" + name)
    i = i+1
    
    i = _ReplaceNodesName(i, node.GetDown(), doc)
    i = _ReplaceNodesName(i, node.GetNext(), doc)
    
    return i

def ApplyExportSelectionHub(doc, lastUUID):

    if lastUUID == "":return

    def predicate(m) :
        tag = m.GetTag(EXPORT_SELECTION_HUB_PLUGIN_ID)
        if tag != None:
            return tag[SELECTION_UUID] == lastUUID
        else:
            return False

    hasTagObjects = FilterAllObjectsInDoc(doc, predicate)
    
    keepObjectList = list()
    
    if len(hasTagObjects) > 0 :
        tag = hasTagObjects[0].GetTag(EXPORT_SELECTION_HUB_PLUGIN_ID)
        exportInCludeData = tag[EXPORT_LIST]
        for i in range(0,exportInCludeData.GetObjectCount()):
            item = exportInCludeData.ObjectFromIndex(doc, i)
            keepObjectList.append(item)
    
    def predicate2(m) :
    
        def xrefPredicate(p): return p.GetType() == c4d.Oxref
        def removeObjectPredicate(c): return c in keepObjectList
    
        flag1 = True
        flag1 &= not m in keepObjectList
        flag1 &= len(FilterChildObjectsInDoc(m, removeObjectPredicate)) == 0
        flag1 &= len(FilterParentObjectsInDoc(m, removeObjectPredicate)) == 0
        #not in keep list.
        
        flag2 = True
        flag2 &= m.GetTag(UNITY_BASE_SETTING_TAG_ID) != None
        flag2 &= flag2 and len(FilterParentObjectsInDoc(m, xrefPredicate)) == 0
        flag2 = not flag2
        #not base setting object.
        
        return flag1 and flag2
    
    willRemoveList = FilterAllObjectsInDoc(doc, predicate2)
    
    for item in willRemoveList:
        item.Remove()

class ExportDialog(gui.GeDialog):
    
    lastUUID = ""
    
    def CreateLayout(self):
        self.SetTitle(title = "Unity Export")
        
        self.AddStaticText(id = LOADING_LAB_ID, flags = c4d.BFH_LEFT, initw = 600, name = "Log:N/A")
        
        self.GroupBegin(id = NONE_ID, flags = c4d.BFH_LEFT, groupflags = c4d.BFV_GRIDGROUP_EQUALROWS)
        self.AddStaticText(id = NONE_ID, flags = c4d.BFH_LEFT, name = "Export Selection Hub(Nullable)")
        self.AddCustomGui(id = SELECTION_HUB_ID, pluginid = c4d.CUSTOMGUI_LINKBOX, flags = c4d.BFH_LEFT, name = "", minw = 400, minh = 5)
        self.GroupEnd()
        
        self.AddButton(id = EXECUTE_BTN_ID, flags = c4d.BFH_LEFT, initw = 600, name = "Execute")
        
        return True

    def Command(self, id, msg):

        doc = c4d.documents.GetActiveDocument()

        if not doc:
            return True

        if id == SELECTION_HUB_ID:
            linkBox = self.FindCustomGui(id = SELECTION_HUB_ID, pluginid = c4d.CUSTOMGUI_LINKBOX)
            linkedData = linkBox.GetLink(doc, EXPORT_SELECTION_HUB_PLUGIN_ID)
            
            if linkedData == None:
                linkBox.SetLink(None)
                gui.MessageDialog("Please Set 'Unity Selection Hub' Tag!")
                return True

            newUUID = uuid.uuid1()
            linkedData[SELECTION_UUID] = str(newUUID)
            self.lastUUID = str(newUUID)
            #Through uuid match shadow file.

        if id == EXECUTE_BTN_ID:
            
            sys.setrecursionlimit(1000000)
            
            baseSettingTag = GetFirstTagInDoc(doc, UNITY_BASE_SETTING_TAG_ID)

            if not baseSettingTag == None and not IsParentXrefObject(baseSettingTag.GetObject()):
                
                path = baseSettingTag[SAVE_PATH]
                
                #Check Output Path
                if path == None:
                    gui.MessageDialog("Please Set 'Save Path' On The 'Unity Base Setting' Tag!")
                    return True
            
                #Check File Extensions
                if len(os.path.splitext(path)[1]) == 0:
                    gui.MessageDialog("Please Attach File extensions such as 'xx.fbx'")
                    return True
                    
                #Check Root Bone
                if baseSettingTag[IS_ENABLE_ROOT_MOTION]:
                    if baseSettingTag[c4d.ROOT_BONE_PATH] == "" or baseSettingTag[c4d.ROOT_BONE_PATH] == None:
                        gui.MessageDialog("Please Set 'Root Bone Path'!")
                        return True
                
                oriTime = time.time()
                
                def timeSub():
                    return time.strftime("%M:%S", time.localtime(time.time()-oriTime))
                
                #------------------------Special 'Pure Output'
                if baseSettingTag[PURE_OUTPUT]:
                
                    dirPath = os.path.dirname(path)
                    fileSourcePath = os.path.join(doc.GetDocumentPath(), doc.GetDocumentName())
                    docDir = os.path.dirname(fileSourcePath)
                    os.chdir(docDir)
                    
                    if baseSettingTag[IS_OUTPUT_MAT_AND_TEXTURE]:
                        ExportLinkedTextures(doc, dirPath)
                    
                    absPath = os.path.abspath(path)
                    self.SetString(LOADING_LAB_ID, "[{0}](1/2)Export FBX File..."
                    .format(timeSub()))
                    
                    try:
                        if not os.path.exists(dirPath):
                            os.makedirs(dirPath)
                    except:
                        print "Create Path Error!"
                    
                    ghostObj = CreateGhostAnimationObject(doc, baseSettingTag[RETENTION_TIME_LENGTH])
                    
                    c4d.documents.SaveDocument(doc, absPath, c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST,1026370)#Export FBX
                    
                    self.SetString(LOADING_LAB_ID, "[{0}](2/2)Output Completed!"
                    .format(timeSub()))

                    self.lastUUID = ""
                    ghostObj.Remove()
                    gui.MessageDialog("Unity Export Completed!")
                    c4d.StatusClear()
                    return True
                #------------------------------------------
                
                self.SetString(LOADING_LAB_ID, "[{0}](1/4)Clone To Shadow Document..."
                .format(timeSub()))
                
                fileSourcePath = os.path.join(doc.GetDocumentPath(), doc.GetDocumentName())

                newDoc, oldDocPath = CloneDoc(doc)

                ApplyExportSelectionHub(newDoc, self.lastUUID)

                baseSettingTag = GetFirstTagInDoc(newDoc, UNITY_BASE_SETTING_TAG_ID)
                path = baseSettingTag[SAVE_PATH]
                rootBonePath = baseSettingTag[ROOT_BONE_PATH]
                isOutputMatAndTex = baseSettingTag[IS_OUTPUT_MAT_AND_TEXTURE]
                frameRate = newDoc.GetFps()
                isRetentionTimeLength = baseSettingTag[RETENTION_TIME_LENGTH]
                
                docDir = os.path.dirname(fileSourcePath)
                os.chdir(docDir)
                
                absPath = os.path.abspath(path)
                dirPath = os.path.dirname(absPath)

                #--------------------RenderOff detection
                baseSettingObjRenderOff = False
                baseSettingObj = baseSettingTag.GetObject()

                if baseSettingObj.GetRenderMode() == c4d.MODE_OFF:
                    baseSettingObj.SetRenderMode(c4d.MODE_UNDEF)
                    baseSettingObjRenderOff = True
                #--------------------

                self.SetString(LOADING_LAB_ID, "[{0}](2/4)Replace And Collapse Objects..."
                .format(timeSub()))

                CreateGhostAnimationObject(newDoc, isRetentionTimeLength)

                RenderOffObjectRemove(newDoc)#Render off main detection

                NodeXrefReplace(newDoc)

                CharacterObjectReplace(newDoc)

                try:
                    if not os.path.exists(dirPath):
                        os.makedirs(dirPath)
                except:
                    print "Create Path Error!"

                if isOutputMatAndTex:
                    ExportLinkedTextures(newDoc, dirPath)
                
                self.SetString(LOADING_LAB_ID, "[{0}](3/4)Start Build To Clipboard Info..."
                .format(timeSub()))
                
                #-----------------------------------------------------------------------------------------------------Build String To Clipboard
                fileHeadStr = "C4DData="

                animationInfoStr = BuildAnimationInfoString(newDoc)#Build Animation Info String
                animationEventInfoStr = BuildAnimationEventInfoString(newDoc)#Build Animation Event Info String
                animatorCurveStr = BuildAnimatorCurveString(newDoc)#Build Animator Curve Info String
                staticInfoStr = BuildStaticInfoString(newDoc)#Build Static Info String
                #-------------------------Build Material String
                materialInfoStr = ""
                if isOutputMatAndTex:
                    materialInfoStr = BuildMaterialInfoString(newDoc)
                #-------------------------

                rootBoneObj = BuildBaseInfoString_GetRootBoneObject(newDoc, rootBonePath)#Build base info string , Get will rename object.
                ReplaceNodesName(newDoc)#Replace name to C2U_... prefix
                baseInfoStr = BuildBaseInfoString(fileSourcePath, rootBoneObj, baseSettingTag, frameRate)#Build Base Info String
            
                finalStr = baseInfoStr + "|" + animationInfoStr + "|" + animationEventInfoStr + "|" + animatorCurveStr + "|" + staticInfoStr + "|" + materialInfoStr

                self.SetString(LOADING_LAB_ID, "[{0}](4/4)Export FBX File..."
                .format(timeSub()))
                
                if baseSettingObjRenderOff:
                    baseSettingObj.Remove()

                c4d.documents.SaveDocument(newDoc, absPath, c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST,1026370)#Export FBX
                
                dir = os.path.dirname(absPath)
                analyzeInfoPath = os.path.join(dir, "AnalyzeInfo")
                
                f = codecs.open(analyzeInfoPath, 'w', 'utf-8')
                f.write(finalStr)

                #------------------------------------------------------------------------------------------------------------------------------
                
                c4d.documents.KillDocument(newDoc)
                LoadAndActiveDoc(oldDocPath)
                
                self.SetString(LOADING_LAB_ID, "[{0}](4/4)Output Completed!"
                .format(timeSub()))
                
                self.lastUUID = ""
                
                c4d.CopyStringToClipboard(fileHeadStr + analyzeInfoPath)
                gui.MessageDialog("Unity Export Completed!")
                c4d.StatusClear()
                
            else:
                gui.MessageDialog("You Must Set 'Unity Base Setting' Tag!")
            
        c4d.EventAdd()
        
        return True

class Entry(plugins.CommandData):

    dialog = None

    def Execute(self, doc):
        if self.dialog is None:
           self.dialog = ExportDialog()

        return self.dialog.Open(dlgtype = c4d.DLG_TYPE_ASYNC, pluginid = PLUGIN_ID)

    def RestoreLayout(self, sec_ref):
        if self.dialog is None:
           self.dialog = ExportDialog()

        return self.dialog.Restore(pluginid = PLUGIN_ID, secret = sec_ref)
        
if __name__ == "__main__":
    bmp = bitmaps.BaseBitmap()
    dir, file = os.path.split(__file__)
    fn = os.path.join(dir, "res", "tag.png")
    bmp.InitWith(fn)
    plugins.RegisterCommandPlugin(id = PLUGIN_ID, str = "UnityExport",
                                info = 0, icon = bmp, 
                                help = "Unity Export",
                                dat = Entry())